
// Header
#include <moose.h>

static bool s_moose_json_deserialize_object(moose_json_object** moose_json_object_ptr, const char* str, unsigned int len);

/**
 * Check whether it is valid of str.
 */
static bool s_moose_json_check(const char* str, unsigned int len, moose_json_type type)
{
    // printf("[s_moose_json_check] str: %s, type: %d\n", str, (int)type);
    switch (type)
    {
        case e_moose_json_type_str:
            return (bool)(str[0] == '"' && str[len-1] == '"');
            break;
        case e_moose_json_type_bool:
            return moose_string_is_bool(str, len);
            break;
        case e_moose_json_type_int:
            return moose_string_is_number(str, len);
            break;
        case e_moose_json_type_arr:
            return (bool)(str[0] == '[' && str[len-1] == ']');
            break;
        case e_moose_json_type_object:
            break;
        case e_moose_json_type_json:
            return (bool)(str[0] == '{' && str[len-1] == '}');
            break;
        default:
            printf("[s_moose_json_check] occur error. str: %s, type: %d\n", str, (int)type);
            break;
    };

    return false;
}

/**
 * Get type of @str
 */
static moose_json_type s_moose_json_get_type(const char* str, unsigned int len)
{
    switch (str[0])
    {
        case '"':
            return e_moose_json_type_str;
            break;
        case '[':
            return e_moose_json_type_arr;
            break;
        case '{':
            return e_moose_json_type_json;
            break;
    };

    if (moose_string_is_number(str, len))
        return e_moose_json_type_int;

    if (moose_string_is_bool(str, len))
        return e_moose_json_type_bool;

    return e_moose_json_type_unknow;
}

static void s_moose_json_assign_key(moose_json_key* key, const char* str, unsigned int len)
{
    key->type = s_moose_json_get_type(str, len);
    switch (key->type)
    {
        case e_moose_json_type_str:
            // Filter a ' " '
            key->key_str = (char*)calloc(1, len-1);
            memcpy(key->key_str, str + 1, len-2);
            break;
        case e_moose_json_type_bool:
            key->key_bool = (strncmp(str, "true", 4) == 0);
            break;
        case e_moose_json_type_int:
            key->key_int = strtoll(str, NULL, 10);
            break;
        default:
            printf("[s_moose_json_assign_key] occur error. str: %s, len: %d\n", str, len);
            break;
    };
}

static void s_moose_json_assign_value(moose_json_value* value, const char* str, unsigned int len)
{
    value->type = s_moose_json_get_type(str, len);
    switch (value->type)
    {
        case e_moose_json_type_str:
            // Filter a ' " '
            value->value_str = (char*)calloc(1, len-1); /* Ensure \0 */
            memcpy(value->value_str, str + 1, len-2);
            break;
        case e_moose_json_type_bool:
            value->value_bool = (strncmp(str, "true", 4) == 0);
            break;
        case e_moose_json_type_int:
            value->value_int = strtoll(str, NULL, 10);
            break;
        case e_moose_json_type_arr:
        case e_moose_json_type_json:
            {
                // Filter '[/{'
                moose_json_object* a_object = (moose_json_object*)calloc(1, sizeof(moose_json_object));
                if (NULL == a_object)
                {
                    return ;
                }

                if (s_moose_json_deserialize_object(&a_object, str + 1, len - 2) == false)
                {
                    free(a_object);
                    return ;
                }

                // Attach to list
                if (value->list == NULL)
                    value->list = moose_list_malloc();
                else
                    moose_list_get_last(value->list)->next = moose_list_malloc();

                moose_list_get_last(value->list)->data = a_object;
            }
            break;
                default:
            printf("[s_moose_json_assign_value] occur error. str: %s, len: %d\n", str, len);
            break;
            };
    }

/**
 * Deserialize object of json from @str.
 * The all arguments will need enable.
 */
static bool s_moose_json_deserialize_object(moose_json_object** moose_json_object_ptr, const char* str, unsigned int len)
{
    moose_json_object* a_object = *moose_json_object_ptr;

    int a_next_index = moose_string_split(str, ':');
    if (a_next_index <= 0)
    {
        return false;
    }

    // The key is maybe empty
    // e.g: cities:["a", "b", "c"]
    if (a_next_index <= len - 1)
    {
        s_moose_json_assign_key(&(a_object->key), str, a_next_index);
    }
    s_moose_json_assign_value(&(a_object->value), str + a_next_index + 1, len - a_next_index - 1);

    return true;
}

/**
 * Deserialize json from str, The moose_json_ptr & str must be enable.
 */
static bool s_moose_json_deserialize(moose_json** moose_json_ptr, const char* str, unsigned int len)
{
    moose_json* a_moose_json = *moose_json_ptr;

    // Check whether it is json
    if (s_moose_json_check(str, len, e_moose_json_type_json) == false)
    {
        printf("[s_moose_json_check] str is not json.\n");
        return false;
    }

    // Clear {} of json
    char* json_str = (char*)calloc(1, len); /* Ensure \0 */
    char* free_json_str = json_str;

    memcpy(json_str, str + 1, len - 2);

    // Circle deserialize str of object
    // a_next_index: The next index of ','.
    int a_next_index = 0;
    while((a_next_index = moose_string_split(json_str, ',')) > 0)
    {
        moose_json_object* a_object = (moose_json_object*)calloc(1, sizeof(moose_json_object));
        if (NULL == a_object)
        {
            free(free_json_str);
            return false;
        }

        if (s_moose_json_deserialize_object(&a_object, json_str, a_next_index) == false)
        {
            free(a_object);
            continue;
        }

        // Attach to list
        if (a_moose_json->list == NULL)
            a_moose_json->list = moose_list_malloc();
        else
            moose_list_get_last(a_moose_json->list)->next = moose_list_malloc();

        moose_list_get_last(a_moose_json->list)->data = a_object;

        // Rebuild str.
        json_str += a_next_index;
        json_str += 1; /* , */
    }

    free(free_json_str);

    return true;
}

/**
 * Create moose_json object with str, return NULL if it occur error.
 * If str is NULL, Create empty moose_json object.
 *
 */
moose_json* moose_json_malloc(const char* str, unsigned int len)
{
    moose_json* a_moose_json = (moose_json*)calloc(1, sizeof(moose_json));

    if (a_moose_json == NULL)
        return NULL;

    if (NULL == str)
        return a_moose_json; 

    // Init list of moose_json
    // a_moose_json->list = moose_list_malloc();

    if (s_moose_json_deserialize(&a_moose_json, str, len) == false)
    {
        moose_json_free(a_moose_json);
        a_moose_json = NULL;
    }

    return a_moose_json; 
}

/**
 * Release moose_json_object
 */
static void s_moose_json_object_free(moose_list* list, void* arg)
{
    moose_json_object* moose_json_object_ptr = (moose_json_object*)list->data;

    if (moose_json_object_ptr == NULL)
    {
        // printf("[moose_json_object_free] moose_json_object_ptr == NULL.\n");
        return ;
    }
    // Free key str.
    if (moose_json_object_ptr->key.type == e_moose_json_type_str)
        free(moose_json_object_ptr->key.key_str);

    // Free value str.
    if (moose_json_object_ptr->value.type == e_moose_json_type_str)
        free(moose_json_object_ptr->value.value_str);

    // Free value arr/json
    switch (moose_json_object_ptr->value.type)
    {
        case e_moose_json_type_arr:
        case e_moose_json_type_json:
            MOOSE_JSON_FOREACH(moose_json_object_ptr->value.list, s_moose_json_object_free, arg);
            // free(moose_json_object_ptr->value.list);
            break;
        default:
            // printf("[moose_json_object_print] occur error. str: %s, len: %d\n", str, len);
            break;
    };

    free(moose_json_object_ptr);
    free(list);
}

/**
 * Release moose_json
 */
void moose_json_free(moose_json* moose_json_ptr)
{
    MOOSE_JSON_FOREACH(moose_json_ptr->list, s_moose_json_object_free, NULL);
    // free(moose_json_ptr->list);
    free(moose_json_ptr);
    moose_json_ptr = NULL;
}

/**
 * Put a object to moose_json_list
 */
moose_list* moose_json_put(moose_list* moose_json_list_ptr, moose_json_key* key, moose_json_value* value)
{
    moose_json_object* a_object = (moose_json_object*)calloc(1, sizeof(moose_json_object));
    if (NULL == a_object)
    {
        return NULL;
    }
    memcpy(&(a_object->key),    key,    sizeof(moose_json_key));
    memcpy(&(a_object->value),  value,  sizeof(moose_json_value));

    // Attach to list
    moose_list_get_last(moose_json_list_ptr)->data = a_object;
    moose_list_get_last(moose_json_list_ptr)->next = moose_list_malloc();
    return moose_json_list_ptr;
}

/**
 * Get a object from moose_json
 */
static bool s_moose_json_compare_key(moose_json_key* k1, moose_json_key* k2)
{
    // type
    if (k1->type != k2->type) return false;

    // Data
    switch (k1->type)
    {
        case e_moose_json_type_str:
            return strcmp(k1->key_str, k2->key_str) == 0;
        case e_moose_json_type_bool:
            return k1->key_bool == k2->key_bool;
        case e_moose_json_type_int:
            return k1->key_int == k2->key_int;
        case e_moose_json_type_unknow:
        case e_moose_json_type_arr:
        case e_moose_json_type_object:
        case e_moose_json_type_json:
            break;
    };

    return false;
}
moose_json_value* moose_json_get(moose_list* moose_json_list_ptr, moose_json_key* key)
{
    moose_list* next = moose_json_list_ptr;
    while (next != NULL)
    {
        moose_json_object* a_object = (moose_json_object*)next->data;
        if (s_moose_json_compare_key(key, &(a_object->key)))        
            return &(a_object->value);
    }
    return NULL;
}

/**
 * Print moose_json_object
 */
static moose_json_object* s_moose_json_object_print(moose_list* list, void* arg)
{
    moose_json_object* moose_json_object_ptr = (moose_json_object*)list->data;

    if (moose_json_object_ptr == NULL)
    {
        return NULL;
    }
    // Print key
    switch (moose_json_object_ptr->key.type)
    {
        case e_moose_json_type_str:
            printf("\"%s\":", moose_json_object_ptr->key.key_str);
            break;
        case e_moose_json_type_bool:
            printf("%s:", moose_json_object_ptr->key.key_bool?"true":"false");
            break;
        case e_moose_json_type_int:
            printf("%ld:", moose_json_object_ptr->key.key_int);
            break;
        default:
            // printf("[moose_json_object_print] occur error. str: %s, len: %d\n", str, len);
            break;
    };

    // Print value str.
    switch (moose_json_object_ptr->value.type)
    {
        case e_moose_json_type_str:
            printf("\"%s\"", moose_json_object_ptr->value.value_str);
            break;
        case e_moose_json_type_bool:
            printf("%s", moose_json_object_ptr->value.value_bool?"true":"false");
            break;
        case e_moose_json_type_int:
            printf("%ld", moose_json_object_ptr->value.value_int);
            break;
        case e_moose_json_type_arr:
            printf("[");
            MOOSE_JSON_FOREACH(moose_json_object_ptr->value.list, s_moose_json_object_print, arg);
            printf("]");
            break;
        case e_moose_json_type_json:
            printf("{");
            MOOSE_JSON_FOREACH(moose_json_object_ptr->value.list, s_moose_json_object_print, arg);
            printf("}");
            break;
        default:
            // printf("[moose_json_object_print] occur error. str: %s, len: %d\n", str, len);
            break;
    };
    // Has a next data
    if (list->next != NULL)
        printf(",");

    return moose_json_object_ptr;
}
/**
 * Print Moose Json
 */
moose_json* moose_json_print(moose_json* moose_json_ptr)
{
    printf("{");
    MOOSE_JSON_FOREACH(moose_json_ptr->list, s_moose_json_object_print, NULL);
    printf("}\n");
    return moose_json_ptr;
}

/**
 * Serialize Moose Json
 */
typedef struct {
    char*   buf;
    size_t  size;
}_moose_json_serialize_data;
static moose_json_object* s_moose_json_object_serialize(moose_list* list, void* arg)
{
    moose_json_object* moose_json_object_ptr = (moose_json_object*)list->data;

    if (moose_json_object_ptr == NULL)
    {
        return NULL;
    }

    _moose_json_serialize_data* a_data = (_moose_json_serialize_data*)arg;

    // Print key
    switch (moose_json_object_ptr->key.type)
    {
        case e_moose_json_type_str:
            a_data->size += sprintf(a_data->buf + a_data->size, "\"%s\":", moose_json_object_ptr->key.key_str);
            break;
        case e_moose_json_type_bool:
            a_data->size += sprintf(a_data->buf + a_data->size, "%s:", moose_json_object_ptr->key.key_bool?"true":"false");
            break;
        case e_moose_json_type_int:
            a_data->size += sprintf(a_data->buf + a_data->size, "%ld:", moose_json_object_ptr->key.key_int);
            break;
        default:
            // printf("[moose_json_object_print] occur error. str: %s, len: %d\n", str, len);
            break;
    };

    // Print value str.
    switch (moose_json_object_ptr->value.type)
    {
        case e_moose_json_type_str:
            a_data->size += sprintf(a_data->buf + a_data->size, "\"%s\"", moose_json_object_ptr->value.value_str);
            break;
        case e_moose_json_type_bool:
            a_data->size += sprintf(a_data->buf + a_data->size, "%s", moose_json_object_ptr->value.value_bool?"true":"false");
            break;
        case e_moose_json_type_int:
            a_data->size += sprintf(a_data->buf + a_data->size, "%ld", moose_json_object_ptr->value.value_int);
            break;
        case e_moose_json_type_arr:
            a_data->size += sprintf(a_data->buf + a_data->size, "[");
            MOOSE_JSON_FOREACH(moose_json_object_ptr->value.list, s_moose_json_object_serialize, arg);
            a_data->size += sprintf(a_data->buf + a_data->size, "]");
            break;
        case e_moose_json_type_json:
            a_data->size += sprintf(a_data->buf + a_data->size, "{");
            MOOSE_JSON_FOREACH(moose_json_object_ptr->value.list, s_moose_json_object_serialize, arg);
            a_data->size += sprintf(a_data->buf + a_data->size, "}");
            break;
        default:
            // printf("[moose_json_object_print] occur error. str: %s, len: %d\n", str, len);
            break;
    };
    // Has a next data
    if (list->next != NULL)
        a_data->size += sprintf(a_data->buf + a_data->size, ",");

    return moose_json_object_ptr;
}
moose_json* moose_json_serialize(moose_json* moose_json_ptr, char** str)
{
    _moose_json_serialize_data a_data;
    a_data.buf  = *str;
    a_data.size = 0;

    a_data.size += sprintf(a_data.buf + a_data.size, "{");
    MOOSE_JSON_FOREACH(moose_json_ptr->list, s_moose_json_object_serialize, &a_data);
    a_data.size += sprintf(a_data.buf + a_data.size, "}");

    return moose_json_ptr;
}
